//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.UIDesigner.Monitor
{
    using System;
    using System.Collections.ObjectModel;
    using System.Globalization;
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using Microsoft.Research.ScientificWorkflow.UIDesigner.Commands;
    using Microsoft.Research.ScientificWorkflow.UIDesigner.WpfComposite;
    using Microsoft.Practices.Unity;
    using Microsoft.Research.eResearch.Subscriber.Interfaces;
    using WorkflowApplication.Interfaces;
    using System.Windows.Controls;
    using System.ComponentModel;
    using Microsoft.Research.DataLayer;
    using Microsoft.Research.eResearch.Common.Linq;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using TridentAPI;
    using Microsoft.Research.ScientificWorkflow.TridentComposer;

    /// <summary>
    /// Presentation model for monitor's workflow parameters tab.
    /// </summary>
    public partial class WorkflowParametersPresentationModel : INotifyPropertyChanged
    {
        /// <summary>
        /// Flag indicating data products to be shown or not.
        /// </summary>
        private bool showDataProducts;

        /// <summary>
        /// The workflow id.
        /// </summary>
        private TridentWorkflowModel currentWorkflowModel;

        /// <summary>
        /// Gets or sets the workflow output collection.
        /// </summary>
        /// <value>The workflow output collection.</value>
        public ObservableCollection<WorkflowOutputProperty> WorkflowOutputCollection { get; private set; }

        /// <summary>
        /// Gets or sets the workflow input property collection.
        /// </summary>
        /// <value>The workflow input property collection.</value>
        public ObservableCollection<WorkflowRequiredInputProperty> WorkflowInputPropertyCollection { get; private set; }

        /// <summary>
        /// Gets or sets the view.
        /// </summary>
        /// <value>The view.</value>
        public WorkflowParametersView View { get; private set; }

        /// <summary>
        /// Gets or sets the container.
        /// </summary>
        /// <value>The container.</value>
        public IUnityContainer Container { get; private set; }

        /// <summary>
        /// Gets or sets a value indicating whether to show data products.
        /// </summary>
        /// <value><c>true</c> if data products to be shown; otherwise, <c>false</c>.</value>
        public bool ShowDataProducts
        {
            get
            {
                return this.showDataProducts;
            }
            set
            {
                this.showDataProducts = value;
                this.RaisePropertyChanged("ShowDataProducts");
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WorkflowParametersPresentationModel"/> class.
        /// </summary>
        /// <param name="container">The container.</param>
        /// <param name="view">The view.</param>
        /// <param name="jobCommandProxy">The job command proxy.</param>
        public WorkflowParametersPresentationModel(IUnityContainer container, WorkflowParametersView view, JobCommandProxy jobCommandProxy)
        {
            View = view;

            WorkflowOutputCollection = new ObservableCollection<WorkflowOutputProperty>();
            WorkflowInputPropertyCollection = new ObservableCollection<WorkflowRequiredInputProperty>();

            this.Container = container;
            container.Resolve<IMonitorService>().WorkflowInstanceCreated += delegate
            {
                this.WorkflowOutputCollection.Clear();
                this.WorkflowInputPropertyCollection.Clear();
            };

            container.Resolve<IMonitorService>().InputOutput += OnInputOutputEvent;
            container.Resolve<IMonitorService>().OutputDataProductReceived += new EventHandler<BlackboardMessageReceivedEventArgs>(OnOutputDataProductReceived);
            container.Resolve<IMonitorService>().Clear += OnClearEvent;
            jobCommandProxy.ExecuteCommand.RegisterCommand(new DelegateCommand<object>(p =>
                {
                    this.WorkflowOutputCollection.Clear();
                    this.WorkflowInputPropertyCollection.Clear();
                }));

            View.Model = this;
        }

        /// <summary>
        /// Called when [output data product received].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Research.eResearch.Subscriber.Interfaces.BlackboardMessageReceivedEventArgs"/> instance containing the event data.</param>
        void OnOutputDataProductReceived(object sender, BlackboardMessageReceivedEventArgs e)
        {
            if (IsOutputDataProductEvent(e))
            {
                Guid dataProductId = new Guid(e.IncomingMessage["trident:OutputDataProductID"]);
                this.LookupOrCreate(this.WorkflowOutputCollection, this.Container.Resolve<IDataProductService>().GenerateDataProductItem(dataProductId));
            }
        }

        /// <summary>
        /// Event hander to clear the ActivityCollection
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnClearEvent(object sender, EventArgs e)
        {
            this.WorkflowOutputCollection.Clear();
            this.WorkflowInputPropertyCollection.Clear();
        }

        /// <summary>
        /// Called when [input output event].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Research.eResearch.Subscriber.Interfaces.BlackboardMessageReceivedEventArgs"/> instance containing the event data.</param>
        private void OnInputOutputEvent(object sender, BlackboardMessageReceivedEventArgs e)
        {
            if (IsInputProperty(e) && IsExecutingActivityEvent(e))
            {
                LookupOrCreate(this.WorkflowInputPropertyCollection, e);
            }
            else if (IsInOutProperty(e))
            {
                if (IsExecutingActivityEvent(e))
                {
                    LookupOrCreate(this.WorkflowInputPropertyCollection, e);
                }
            }
        }

        /// <summary>
        /// Determines whether the message is of input type.
        /// </summary>
        /// <param name="e">The <see cref="Microsoft.Research.eResearch.Subscriber.Interfaces.BlackboardMessageReceivedEventArgs"/> instance containing the event data.</param>
        /// <returns>
        /// 	<c>true</c> if [is input property] [the specified e]; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsInputProperty(BlackboardMessageReceivedEventArgs e)
        {
            return e.IncomingMessage["trident:ActivityPropertyDirection"] == "Input";
        }

        /// <summary>
        /// Determines whether the message is of inout type.
        /// </summary>
        /// <param name="e">The <see cref="Microsoft.Research.eResearch.Subscriber.Interfaces.BlackboardMessageReceivedEventArgs"/> instance containing the event data.</param>
        /// <returns>
        /// 	<c>true</c> if [is in out property] [the specified e]; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsInOutProperty(BlackboardMessageReceivedEventArgs e)
        {
            return e.IncomingMessage["trident:ActivityPropertyDirection"] == "InOut";
        }

        /// <summary>
        /// Determines whether the message is executing event.
        /// </summary>
        /// <param name="e">The <see cref="Microsoft.Research.eResearch.Subscriber.Interfaces.BlackboardMessageReceivedEventArgs"/> instance containing the event data.</param>
        /// <returns>
        /// 	<c>true</c> if [is executing activity event] [the specified e]; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsExecutingActivityEvent(BlackboardMessageReceivedEventArgs e)
        {
            return e.IncomingMessage.ContainsKey("trident:ActivityState") && e.IncomingMessage["trident:ActivityState"] == "Executing";
        }

        /// <summary>
        /// Determines whether is output data product event.
        /// </summary>
        /// <param name="e">The <see cref="Microsoft.Research.eResearch.Subscriber.Interfaces.BlackboardMessageReceivedEventArgs"/> instance containing the event data.</param>
        /// <returns>
        /// 	<c>true</c> if [is output data product event] [the specified e]; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsOutputDataProductEvent(BlackboardMessageReceivedEventArgs e)
        {
            return e.IncomingMessage.ContainsKey("trident:OutputDataProductID");
        }

        /// <summary>
        /// Lookups the or create.
        /// </summary>
        /// <param name="workflowInputs">The workflow inputs.</param>
        /// <param name="e">The <see cref="Microsoft.Research.eResearch.Subscriber.Interfaces.BlackboardMessageReceivedEventArgs"/> instance containing the event data.</param>
        private void LookupOrCreate(
            ObservableCollection<WorkflowRequiredInputProperty> workflowInputs,
            BlackboardMessageReceivedEventArgs e)
        {
            string activityUniqueName = e.IncomingMessage["trident:ActivityName"];
            string fieldName = e.IncomingMessage["trident:FieldName"];
            if (this.CheckWorkflowRequiredInput(activityUniqueName, fieldName))
            {
                string displayName = string.Format(
               CultureInfo.InvariantCulture,
               "{0}.{1}",
               e.IncomingMessage["trident:ActivityLabel"],
               fieldName);

                WorkflowRequiredInputProperty requiredInput =
                    new WorkflowRequiredInputProperty { DisplayName = displayName, PropertyName = fieldName, ActivityUniqueName = activityUniqueName };

                int index = workflowInputs.IndexOf(requiredInput);
                if (index == -1)
                {
                    workflowInputs.Add(requiredInput);
                }
                else
                {
                    requiredInput = workflowInputs[index];
                }

                requiredInput.Value = e.IncomingMessage["trident:ActivityPropertyValue"];
            }
        }

        /// <summary>
        /// Creates if not present.
        /// </summary>
        /// <param name="dataproduct">The dataproduct.</param>
        /// <returns></returns>
        private WorkflowOutputProperty LookupOrCreate(ObservableCollection<WorkflowOutputProperty> workflowOutputs, DataProductItem dataproduct)
        {
            WorkflowOutputProperty outputProperty = null;
            try
            {
                if (dataproduct.OriginalDataProduct.IsFinal)
                {
                    Guid dataproductId = (dataproduct.OriginalDataProduct as IObject) != null ? (dataproduct.OriginalDataProduct as IObject).ID : Guid.Empty;

                    foreach (WorkflowOutputProperty item in workflowOutputs)
                    {
                        if (item.Id.Equals(dataproductId))
                        {
                            outputProperty = item;
                            break;
                        }
                    }

                    if (outputProperty == null)
                    {
                        string displayName = string.Format(
                        CultureInfo.InvariantCulture,
                        "{0}.{1}",
                        dataproduct.OriginalDataProduct.CreatorActivity.Activity.Label,
                        dataproduct.OriginalDataProduct.CreatorParameter.ActivityParameter.Name);

                        outputProperty = new WorkflowOutputProperty
                        {
                            Id = dataproductId,
                            DisplayName = displayName,
                            ActivityName = dataproduct.OriginalDataProduct.CreatorActivity.Activity.Name,
                            ParameterName = dataproduct.OriginalDataProduct.CreatorParameter.ActivityParameter.Name
                        };

                        workflowOutputs.Add(outputProperty);
                    }

                    outputProperty.DataProductName = dataproduct.OriginalDataProduct.Name;
                    outputProperty.Data = dataproduct;
                }
                else if (this.ShowDataProducts)
                {
                    this.DataProductPresentationController.UpdateDataProducts(dataproduct);
                }
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleUIException(ex);
            }

            return outputProperty;
        }

        /// <summary>
        /// Checks the parameter is workflow required input parameter.
        /// </summary>
        /// <param name="activityUniqueName">Name of the activity unique.</param>
        /// <param name="parametername">The parametername.</param>
        /// <returns></returns>
        private bool CheckWorkflowRequiredInput(string activityUniqueName, string parametername)
        {
            bool isWorkflowRequiredInput = false;

            BaseModel activityModel = this.Container.Resolve<UIHelper>("UiHelper").MonitorWorkflowModel.ModelsHashtable[activityUniqueName] as BaseModel;

            if (activityModel != null)
            {
                foreach (ParameterDescriptionModel paramModel in activityModel.InputParameters)
                {
                    if (paramModel.Name.Equals(parametername) && paramModel.IsMandatoryForWorkflow)
                    {
                        isWorkflowRequiredInput = true;
                        break;
                    }
                }
            }

            return isWorkflowRequiredInput;
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the property changed.
        /// </summary>
        /// <param name="name">The name.</param>
        protected void RaisePropertyChanged(string name)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion
    }
}
